C++ pointers are a powerful feature, and it is easy to make mistakes with them. In what follows we discuss some common pointer problems that all programmers need to be on the lookout for. ## Pointer Addresses are Unpredictable Your program should *never* assume that the value of a pointer is the same from run to run, or even within the same program. For example: ```cpp void f(const string& msg) {     int n = 25;     int* p = &n;   // p points to n, i.e. p stores the address                    // of n     cout << p     // Careful! This prints unpredictable values          << msg    // because there is no guarantee where in          << "\n";  // memory n is stored. } void different_address_demo2() {     f(" (third call))");     f(" (fourth call)"); } void different_address_demo() {     cout << "different_address_demo ...\n";     //     // There is no guarantee what the calls to f print. The address of n     // inside f may be different on different runs.     //     // The first two calls print the same thing on my machine,     // but that is not guaranteed.     //     // The third and fourth calls (called within     // different_address_demo2) print the same thing, but the address     // is different than the first two calls.     f(" (first call))");     f(" (second call)");     different_address_demo2(); } ``` > **Rule of Thumb** Never assume assume a pointer has a particular value. ## Dangling Pointers 1 A pointer should always point to a valid memory location. If not, we say it is a [[dangling pointer]]. For example: ```cpp void dangling_pointer_demo1() {     cout << "dangling_pointer_demo1 ...\n";         int* p = new int(25);  // p points to a new int on the free store         // ... do things with p ...     delete p;              // Okay: we're done with p so we de-allocate     // the memory it points to.                            // But: p is now a dangling pointer, i.e. the                            // memory it points to is de-allocated and                            // should not be used in any way     *p = 42;               // ERROR! The memory p points to is not     // allocated, so this is not allowed. While it     // *may* appear to work correctly (the next     // cout line may print 42), it writes to memory     // we are not allowed to write to.     cout << "*p: " << *p   // ERROR! Prints 42 for me. But that doesn't     << "\n"; // mean it's correct! The memory p points to     // is not allocated, and so there is no     // guarantee what is there. It may be 42, but     // it may be something else. //                            // valgrind catches this error, calling it an                            // "invalid write". } ``` The [[valgrind]] tool can help you find dangling pointers, but it can't find all such errors. In C++, it's ultimately up to the programmer to make sure they never use dangling pointers. We will see an interesting solution to the problem later in the course: objects with constructors and destructors can automatically allocate and de-allocate memory at the right time, avoid dangling pointers. > **Rule of Thumb** Never read or write through a pointer if it is de-allocated. ## Dangling Pointers 2 Here is another example of a [[dangling pointer]]. `exclaim_with_a_bug` is meant to return a pointer to a copy of `s` with an `'!'` on the end, but it has a serious bug: ```cpp string* exclaim_with_a_bug(const string& s) {     string result = s + "!";     return &result;    // ERROR! Should never return a pointer to a local     // variable. The memory it points to is     // de-allocated when the function ends, and so the     // returned pointer is a dangling pointer. } void dangling_pointer_demo2() {     string* p = exclaim_with_a_bug("hello");  // ERROR! p is a     // dangling pointer         cout << *p << "\n"; // ERROR! } ``` Fortunately, this example does *not* compile if you use the course makefile. The course makefile turns on options that  will catch this error at compile-time. > **Rule of Thumb** Never return a pointer to a local variable. ## Memory Leaks A **memory leak** occurs when a program allocates memory on the free store, but then never de-allocates it. For example: ```cpp void memory_leak_demo() {     int* p = new int(25);  // p points to a new int on the free store     // okay to use p ...     cout << " p: " << p << "\n";     cout << "*p: " << *p << "\n"; }  // Error! We didn't de-allocate the memory p points to. // Running this with valgrind will catch the memory leak. ``` When the function ends, the local variable `p` disappears (it is popped from the [[stack memory|call stack]]), but the value it points to on the free store is still there. This is a [[memory leak]], i.e. the free store memory will stay allocated until the program ends. [[Memory leak]]s waste memory. Wasted memory might not be a problem in short-running programs, or if only a little bit of memory is wasted, but in long-running programs even a small memory leak can add up to cause the program to slow down, or even crash. [[valgrind]] can catch memory leaks in a running C++ program with nearly perfect accuracy. It is up to the programmer, though, to determine the root cause of the leak, and how best to fix it. > **Rule of Thumb** Always de-allocate memory you have allocated, when you are done with it. ## Double Deletion 1 Double deletion is a kind of [[dangling pointer]] problem. When you de-allocate memory on the free store with `delete`/`delete[]`, you should de-allocate it exactly *once*. It's an error if you de-allocate it twice or more. For example: ```cpp void double_deletion_demo1() {     int* p = new int(25);  // ok: p points to a new int on the free store     // okay, use p ...     cout << " p: " << p << "\n";     cout << "*p: " << *p << "\n";     delete p;  // ok: we're done with p, de-allocate it     // At this point p is a dangling pointer, i.e. it is pointing to     // de-allocated memory. We should not use it in any way for anything,     delete p; // ERROR! Deleting p again is a double deletion, and is not               // allowed. It could corrupt the entire free store memory               // system.     // Running without valgrind crashes for me, saying a "double free" was     // detected. That's good that it caught the problem and gave an     // understandable error message, but programs should never crash. We     // should catch such errors at compile-time (or earlier!). } ``` > **Rule of Thumb** Always de-allocate memory you have allocated exactly one time. ## Double Deletion 2 Double deletion can be hard to catch when you have multiple pointers to the same memory. For example: ```cpp void double_deletion_demo2() {     int* p = new int(25);  // p points to a new int on the free store     int* q = p;            // q points to the same thing as p     // use p and q, no problems     cout << " p: " << p << "\n";     cout << "*p: " << *p << "\n";     cout << " q: " << q << "\n";     cout << "*q: " << *q << "\n";     delete p;  // ok: we're done with p, so de-allocate it     // At this point p and q are both dangling pointers, i.e. they are     // pointing to de-allocated memory. We should not use them in any way     // for anything,   delete q; // ERROR! q points to the same thing as p, so deleting q   // is a double deletion!     // Running without valgrind crashes for me, saying a "double free" was     // detected. That's good that it caught the problem and gave an     // understandable error message, but programs should never crash. We     // should catch such errors at compile-time (or earlier!). } ``` In C++, it is the *programmers responsibility* to be sure that they are properly de-allocating memory. Tools like [[valgrind]] make it easier to catch such errors, but it can't always tell you the root cause, or how to fix it. ## Passing Pointers by Value In C++, you can pass variables to functions using either [[pass by value]] or [[pass by reference]]. For example: ```cpp double g(double a);  // a passed by value                      // g gets a copy of the value in a double h(double& b); // b passed by reference                      // h gets a reference to b, not a copy;                      // h can change the value of b ``` Pass by reference can also be simulated with pointers. For example, suppose we want a function that swaps the values of two integers. Pass by value doesn't work: ```cpp // a and b are passed by value void swap1_bad(int a, int b) {     int temp = a;     a = b;     b = temp; } void swap1_bad_demo() {     int x = 5;     int y = 10;     cout << x << "\n";  // 5     cout << y << "\n";  // 10 swap1_bad(x, y);     cout << x << "\n";  // 5     cout << y << "\n";  // 10     // x and y are not swapped! That's because they are passed by value to     // swap1_bad, i.e. a copy of their values is passed, and those copies     // are swapped. The original x and y values are not changed. } ``` We can get around this problem by passing *pointers* to the variables we want to swap: ```cpp // a and b are passed by value, but they are pointers to ints and so the // pointer values are copied, but the values they point to are not copied void swap2(int* a, int* b) {     int temp = *a;     *a = *b;     *b = temp; } void swap2_demo() {     int x = 5;     int y = 10;     cout << x << "\n";  // 5     cout << y << "\n";  // 10     swap2(&x, &y);  // note the &: we must pass the addresses of x and y     cout << x << "\n";  // 10     cout << y << "\n";  // 5 } ``` This works, and it is how the C language handles cases where you want to pass by reference. But there are a couple of problems: - We must write `swap2(&x, &y)`, i.e. we have to remember to add the `&`. - The body of `swap2` is a little more complicated because it uses pointers. In C++, references provide a nicer solution: ```cpp // a and b are passed by reference; we treat a and b as if they were // regular ints void swap3(int& a, int& b) {     int temp = a;     a = b;     b = temp; } void swap3_demo() {     int x = 5;     int y = 10;     cout << x << "\n";  // 5     cout << y << "\n";  // 10     swap3(x, y);  // no & needed: pass by reference lets us                   // pass x and y directly     cout << x << "\n";  // 10     cout << y << "\n";  // 5 } ``` > **Rule of Thumb**: Use pass-by-reference instead of pointers whenever possible. ## Practice Questions 1. Why do the values of pointers usually change from run to run of a program? 2. In your own words, describe a [[dangling pointer]]. 3. If `p` is a pointer pointing to valid value, is it always the case that `p` is a [[dangling pointer]] right after calling `delete p`? 4. Explain why it is an error to return a pointer to a local variable. 5. In your own words, describe a [[memory leak]]. 6. What is an example of when a [[memory leak]] might be tolerable? 7. What tool can you use to detect a [[memory leak]] in a running C++ program? 8. In your own words, describe **double deletion**. 9. Explain how **double deletion** is a kind of [[dangling pointer]] problem. 10. What is the default way that C++ passes values to functions? 11. Explain the difference between [[pass by value]] and [[pass by reference]]. 12. Explain how [[pass by reference]] can be simulated using pointers and [[pass by value]]. 13. In C++, why is [[pass by reference]] generally preferred over passing by pointers?